//
//  Paging.m
//  SimulacraDB
//
//  Created by Andre Cohen on 8/10/08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#import "Paging.h"
#import "Record.h"
#import "DataStream.h"


@implementation Paging

@synthesize name;
@synthesize blocksPerPage;
@synthesize blockSize;

- (id)initForData:(NSString *)filename 
		   blocks:(int)block {
	[super init];

	name = filename;
	pages = [[[DataStream alloc] initWithFilename:[NSString stringWithFormat:@"%@.%@",filename,@"spages"]] autorelease];
	data = [[[DataStream alloc] initWithFilename:[NSString stringWithFormat:@"%@.%@",filename,@"sdb"]] autorelease];
	
	if(block==0)
		blockSize = 8;
	else
		blockSize = block;
	blocksPerPage = 16;
	
	return self;
}

- (void)close {
	[pages flush];
	[data flush];
}

- (void)writeRecord:(Record *)record {
	ByteArray *page;
	int currPage = 0;
	int i;
	int size = 0;
	int loc = -1;
	int blocksNeeded = ceil([record size]/(blockSize * 1.0));

	while(size!=blocksNeeded){
		loc = -1;
		page = [self getPageNumber:currPage++];
		for(i=0;i<[page bitCount] && size!=blocksNeeded; i++){
			if([page getBitFromLocation:i]==0){
				size++;
				if(loc<0)
					loc = i;
			}else if(size!=0){
				size = 0;
				loc = -1;
			}
		}
	}
	currPage--;
	[[record wrapper] setRid:[RID createWithPage:currPage offset:loc]];
 	[self writeRecordData:record];
	

	for(i=loc;i<loc+blocksNeeded;i++){
		[page setBitAtLocation:i value:1];
	}
	
	
	[self savePage:page number:currPage];
}

- (void)writeRecordData:(Record *)record {
	[data write:[record toByteArray] at:([[[record wrapper] rid] page])*(blocksPerPage*blockSize)+[[[record wrapper] rid] offset]*blockSize];
}

- (void)removeRecord:(Record *)record {
	ByteArray *page = [self getPageNumber:[[[record wrapper] rid] page]];
	int i;
	for(i=[[[record wrapper] rid] offset];i<[[[record wrapper] rid] offset]+ceil([record size]/(blockSize * 1.0));i++)
		[page setBitAtLocation:i value:0];
	[self savePage:page number:[[[record wrapper] rid] page]];
}

- (int)pageCount {
	int size = [pages fileSize];
	if(size>0)
		return  (size/(blocksPerPage/CHAR_BIT));
	return 0;
}

- (ByteArray *)getPageNumber:(int)page {
	if(page==[self pageCount] || [self pageCount]==0){
		ByteArray *array = [ByteArray createWithSize:blocksPerPage/CHAR_BIT];
		[self savePage:array number:page];
		return array;
	}
	return [pages readFrom:page*(blocksPerPage/CHAR_BIT) length:(blocksPerPage/CHAR_BIT)];
}

- (void)savePage:(ByteArray *)page number:(int)num {
	[pages write:page at:num*(blocksPerPage/CHAR_BIT)];
}

- (Record *)getRecordAt:(Record *)record {
	int page = [[[record wrapper] rid] page];
	int offset = [[[record wrapper] rid] offset];
	
	// Check if record even exists
	if([self getBlockAtPage:page offset:offset]==NO)
		return nil;
	
	return [self readRecordData:record];
}

- (Record *)readRecordData:(Record *)record {
	int location = [[[record wrapper] rid] page]*blocksPerPage*CHAR_BIT + [[[record wrapper] rid] offset]*blockSize;
	int size = 0;
	int i = 0;
	
	// Get wrapper
	ByteArray *array = [data readFrom:location length:[[record wrapper] size]];
	location = location + [[record wrapper] size];
	[[record wrapper] extractWrapper:array];
	
	for(i=0;i<[[record dataBlocks] count];i++){
		// Get data block size
		size = [[data readFrom:location length:sizeof(int)] getIntFromLocation:0];
		location = location + 2;
		
		// Get data block
		array = [data readFrom:location length:size];
		[[record getBlock:i] setValueFromData:array];
		location = location + size;
	}
	
	return record;
}

- (bool)getBlockAtPage:(int)page offset:(int)off {
	ByteArray *array = [self getPageNumber:page];
	return [array getBitFromLocation:off];
}

@end
